package pl.elka.simulator.core.mobilityManager.impl;

import java.util.ArrayList;
import java.util.List;

import pl.elka.simulator.core.EnviromentConfiguration;
import pl.elka.simulator.core.GravityPoint;
import pl.elka.simulator.core.IPaintable;
import pl.elka.simulator.core.Node;
import pl.elka.simulator.core.Position;
import pl.elka.simulator.core.configurationCommand.ConfigurationCommandType;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToGravityPointConfiguration;
import pl.elka.simulator.core.mobilityManager.IMobilityManager;
import pl.elka.simulator.core.mobilityManager.MobilityManagerType;
import pl.elka.simulator.core.mobilityManager.impl.nodes.RWMNode;

public class GravityMobilityManager extends RandomWalkMobilityManager implements IMobilityManager,IAbleToGravityPointConfiguration{

	private static float DEACTIVATE_GRAVITY_POINT = 0.05f;//5 metrow
	private static float DEACTIVATE_GRAVITY_POINT_SQRT = DEACTIVATE_GRAVITY_POINT * DEACTIVATE_GRAVITY_POINT;
	private String id;
	
	@Override
	public IMobilityManager newInstance() {		
		return new GravityMobilityManager();
	}

	@Override
	public MobilityManagerType getType() {
		return MobilityManagerType.GravityMobility;
	}

	@Override
	public List<ConfigurationCommandType> getAllowedConfigurationCommands() {
		List<ConfigurationCommandType> ret = new ArrayList<ConfigurationCommandType>();	
		ret.add(ConfigurationCommandType.AddGravityAttractionPointConfigurationCommand);
		ret.add(ConfigurationCommandType.DeleteGravityAttractionPointConfigurationCommand);
		return ret;
	}		

	@Override
	public void setId(String id) {		
		this.id = id;
	}
	
	@Override
	public String getId() {
		return id;
	}	
	
	@Override
	public List<IPaintable> getPaintaibleElements() {
		List<IPaintable> ret = new ArrayList<IPaintable>();		
		if(gravityPoints!=null){
			for(GravityPoint n:gravityPoints){
				ret.add(n);
			}
		}
		ret.addAll(super.getPaintaibleElements());
		return ret;
	}	
	@Override
	public void applyEnviroment(EnviromentConfiguration cfg) {	
		super.applyEnviroment(cfg);
		for(GravityPoint gp :gravityPoints){
			gp.setActive(true);
		}
	}

	private List<GravityPoint> gravityPoints = new ArrayList<GravityPoint>();
	
	@Override
	public List<GravityPoint> getGravityPoints() {		
		return gravityPoints;
	}
	
	@Override
	protected Position randomVelocity(Node n){
		if(gravityPoints==null || gravityPoints.isEmpty()){
			return super.randomVelocity(n);
		}
		
		float nForce=0,sForce=0,eForce=0,wForce=0;
		
		for(GravityPoint gp:gravityPoints){
			if(gp.isActive()){
				float x = gp.getPosition().x - n.getPosition().x;
				float y = gp.getPosition().y - n.getPosition().y;
				
				if((x*x+y*y)<DEACTIVATE_GRAVITY_POINT_SQRT){
					gp.setActive(false);
					break;
				}
				
				float fx = 0; //x/(x*x + y*y);
				float fy = 0;//y/(x*x + y*y);
				
				if(x!=0){
					fx = x/(x*x + y*y);//1/(x*x*x*x);//x/(x*x + y*y);
					//if(x<0) fx*=-1;
				}
				if(y!=0){
					fy = y/(x*x + y*y);//1/(y*y*y*y);//y/(x*x + y*y);
					//if(y<0) fy*=-1;
				}
				
				if(fy>0){
					nForce+=fy;
				}
				else{
					sForce-=fy;
				}
				
				if(fx>0){
					eForce+=fx;
				}
				else{
					wForce-=fx;
				}
			}			
		}
		if(nForce==0 && sForce==0 && eForce==0 && wForce==0){
			new Position(0,0);//return super.randomVelocity(n);
		}
		float randomNS=(float)Math.random()*(nForce+sForce);
		float randomEW=(float)Math.random()*(eForce+wForce);
		
		float xForce = 0;
		float yForce = 0;
		
		if(randomNS>sForce){			
			yForce=(randomNS-sForce)/nForce;
		}else{
			yForce=-randomNS/sForce;
		}
		
		if(randomEW>wForce){
			xForce=(randomEW-wForce)/eForce;
		}
		else{
			xForce=-randomEW/wForce;
		}
		
		float yVelocity = (float)(Node.MAX_VELOCITY*yForce/Math.sqrt(xForce*xForce + yForce*yForce));
		float xVelocity = (float)(Node.MAX_VELOCITY*xForce/Math.sqrt(xForce*xForce + yForce*yForce));
		
		return new Position(xVelocity,yVelocity);
	}
	
	@Override
	protected int randomInterval(Node n){
		return 1;
	}

	@Override
	public void setGravityPoints(List<GravityPoint> points) {
		this.gravityPoints = points;
	}
}
